/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.web.taglib.common;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.IterationTag;
import javax.servlet.jsp.tagext.Tag;

import org.springframework.web.servlet.support.RequestContextUtils;

/**
 * A base class to write tags library.
 * 
 * @param <TAG>
 *          the type of the {@link Tag} being wrapped
 */
public class SpringTagSupport<TAG extends Tag>
    implements IterationTag, Tag, BodyTag
{
  /**
   * the {@link PageContext} of the current invocation.
   */
  private transient PageContext pageContext;

  /**
   * the {@link Tag} being facaded.
   */
  private transient TAG tag;

  /**
   * tagName the name of the spring bean containing the tag.
   */
  private final String tagName;



  /**
   * constructor.
   * 
   * @param tagName
   *          the name of the spring bean containing the tag
   */
  protected SpringTagSupport(String tagName)
  {
    this.tagName = tagName;
  }



  /**
   * Process body (re)evaluation. This method is invoked by the JSP Page
   * implementation object after every evaluation of the body into the
   * BodyEvaluation object. The method is not invoked if there is no body
   * evaluation.
   * <p>
   * If doAfterBody returns EVAL_BODY_AGAIN, a new evaluation of the body will
   * happen (followed by another invocation of doAfterBody). If doAfterBody
   * returns SKIP_BODY, no more body evaluations will occur, and the doEndTag
   * method will be invoked.
   * <p>
   * If this tag handler implements BodyTag and doAfterBody returns SKIP_BODY,
   * the value of out will be restored using the popBody method in pageContext
   * prior to invoking doEndTag.
   * <p>
   * The method re-invocations may be lead to different actions because there
   * might have been some changes to shared state, or because of external
   * computation.
   * <p>
   * The JSP container will resynchronize the values of any AT_BEGIN and NESTED
   * variables (defined by the associated TagExtraInfo or TLD) after the
   * invocation of doAfterBody().
   * 
   * @return whether additional evaluations of the body are desired
   * @throws JspException
   *           if an error occurred while processing this tag *
   * @see javax.servlet.jsp.tagext.IterationTag#doAfterBody()
   */
  public int doAfterBody()
      throws JspException
  {
    if (getTag() instanceof IterationTag) {
      return ((IterationTag) getTag()).doAfterBody();
    }
    else {
      return SKIP_BODY;
    }
  }



  /**
   * Process the end tag for this instance. This method is invoked by the JSP
   * page implementation object on all Tag handlers.
   * <p>
   * This method will be called after returning from doStartTag. The body of the
   * action may or may not have been evaluated, depending on the return value of
   * doStartTag.
   * <p>
   * If this method returns EVAL_PAGE, the rest of the page continues to be
   * evaluated. If this method returns SKIP_PAGE, the rest of the page is not
   * evaluated, the request is completed, and the doEndTag() methods of
   * enclosing tags are not invoked. If this request was forwarded or included
   * from another page (or Servlet), only the current page evaluation is
   * stopped.
   * <p>
   * The JSP container will resynchronize the values of any AT_BEGIN and AT_END
   * variables (defined by the associated TagExtraInfo or TLD) after the
   * invocation of doEndTag().
   * 
   * @return indication of whether to continue evaluating the JSP page.
   * @throws JspException
   *           if an error occurred while processing this tag
   * @see javax.servlet.jsp.tagext.Tag#doEndTag()
   */
  public int doEndTag()
      throws JspException
  {
    return getTag().doEndTag();
  }



  /**
   * Process the start tag for this instance. This method is invoked by the JSP
   * page implementation object.
   * <p>
   * The doStartTag method assumes that the properties pageContext and parent
   * have been set. It also assumes that any properties exposed as attributes
   * have been set too. When this method is invoked, the body has not yet been
   * evaluated.
   * <p>
   * This method returns Tag.EVAL_BODY_INCLUDE or BodyTag.EVAL_BODY_BUFFERED to
   * indicate that the body of the action should be evaluated or SKIP_BODY to
   * indicate otherwise.
   * <p>
   * When a Tag returns EVAL_BODY_INCLUDE the result of evaluating the body (if
   * any) is included into the current "out" JspWriter as it happens and then
   * doEndTag() is invoked.
   * <p>
   * BodyTag.EVAL_BODY_BUFFERED is only valid if the tag handler implements
   * BodyTag.
   * <p>
   * The JSP container will resynchronize the values of any AT_BEGIN and NESTED
   * variables (defined by the associated TagExtraInfo or TLD) after the
   * invocation of doStartTag(), except for a tag handler implementing BodyTag
   * whose doStartTag() method returns BodyTag.EVAL_BODY_BUFFERED.
   * 
   * @return EVAL_BODY_INCLUDE if the tag wants to process body, SKIP_BODY if it
   *         does not want to process it.
   * @throws JspException
   *           if an error occurred while processing this tag
   * @see javax.servlet.jsp.tagext.BodyTag
   * @see javax.servlet.jsp.tagext.Tag#doStartTag()
   */
  public int doStartTag()
      throws JspException
  {
    return getTag().doStartTag();
  }



  /**
   * Get the parent (closest enclosing tag handler) for this tag handler.
   * <p>
   * The getParent() method can be used to navigate the nested tag handler
   * structure at runtime for cooperation among custom actions; for example, the
   * findAncestorWithClass() method in TagSupport provides a convenient way of
   * doing this.
   * <p>
   * The current version of the specification only provides one formal way of
   * indicating the observable type of a tag handler: its tag handler
   * implementation class, described in the tag-class subelement of the tag
   * element. This is extended in an informal manner by allowing the tag library
   * author to indicate in the description subelement an observable type. The
   * type should be a subtype of the tag handler implementation class or void.
   * This addititional constraint can be exploited by a specialized container
   * that knows about that specific tag library, as in the case of the JSP
   * standard tag library.
   * 
   * @return the current parent, or null if none.
   * @see javax.servlet.jsp.tagext.TagSupport#findAncestorWithClass
   * @see javax.servlet.jsp.tagext.Tag#getParent()
   */
  public Tag getParent()
  {
    return getTag().getParent();
  }



  /**
   * Called on a Tag handler to release state. The page compiler guarantees that
   * JSP page implementation objects will invoke this method on all tag
   * handlers, but there may be multiple invocations on doStartTag and doEndTag
   * in between.
   * 
   * @see javax.servlet.jsp.tagext.Tag#release()
   */
  public void release()
  {
    getTag().release();
  }



  /**
   * Set the current page context. This method is invoked by the JSP page
   * implementation object prior to doStartTag().
   * <p>
   * This value is *not* reset by doEndTag() and must be explicitly reset by a
   * page implementation if it changes between calls to doStartTag().
   * 
   * @param pageContext
   *          The page context for this tag handler.
   * @see javax.servlet.jsp.tagext.Tag#setPageContext(javax.servlet.jsp.PageContext)
   */
  public void setPageContext(PageContext pageContext)
  {
    this.pageContext = pageContext;
    getTag().setPageContext(pageContext);
  }



  /**
   * Set the parent (closest enclosing tag handler) of this tag handler. Invoked
   * by the JSP page implementation object prior to doStartTag().
   * <p>
   * This value is *not* reset by doEndTag() and must be explicitly reset by a
   * page implementation.
   * 
   * @param parent
   *          The parent tag, or null.
   * @see javax.servlet.jsp.tagext.Tag#setParent(javax.servlet.jsp.tagext.Tag)
   */
  public void setParent(Tag parent)
  {
    getTag().setParent(parent);
  }



  /**
   * getter for the tagName property.
   * 
   * @return the tagName
   */
  public String getTagName()
  {
    return tagName;
  }



  /**
   * getter for the pageContext property.
   * 
   * @return the pageContext
   */
  public PageContext getPageContext()
  {
    return pageContext;
  }



  /**
   * getter for the tag property.
   * 
   * @return the tag
   */
  @SuppressWarnings("unchecked")
  protected TAG getTag()
  {
    if (tag == null) {
      tag = (TAG) RequestContextUtils.getWebApplicationContext(getPageContext().getRequest(), getPageContext().getServletContext()).getBean(getTagName());
    }
    return tag;
  }



  /**
   * Prepare for evaluation of the body. This method is invoked by the JSP page
   * implementation object after setBodyContent and before the first time the
   * body is to be evaluated. This method will not be invoked for empty tags or
   * for non-empty tags whose doStartTag() method returns SKIP_BODY or
   * EVAL_BODY_INCLUDE.
   * <p>
   * The JSP container will resynchronize the values of any AT_BEGIN and NESTED
   * variables (defined by the associated TagExtraInfo or TLD) after the
   * invocation of doInitBody().
   * 
   * @throws JspException
   *           if an error occurred while processing this tag
   * @see #doAfterBody
   * @see javax.servlet.jsp.tagext.BodyTag#doInitBody()
   */
  public void doInitBody()
      throws JspException
  {
    if (getTag() instanceof BodyTag) {
      ((BodyTag) getTag()).doInitBody();
    }
  }



  /**
   * Set the bodyContent property. This method is invoked by the JSP page
   * implementation object at most once per action invocation. This method will
   * be invoked before doInitBody. This method will not be invoked for empty
   * tags or for non-empty tags whose doStartTag() method returns SKIP_BODY or
   * EVAL_BODY_INCLUDE.
   * <p>
   * When setBodyContent is invoked, the value of the implicit object out has
   * already been changed in the pageContext object. The BodyContent object
   * passed will have not data on it but may have been reused (and cleared) from
   * some previous invocation.
   * <p>
   * The BodyContent object is available and with the appropriate content until
   * after the invocation of the doEndTag method, at which case it may be
   * reused.
   * 
   * @param bodyContent
   *          the BodyContent
   * @see #doInitBody
   * @see #doAfterBody
   * @see javax.servlet.jsp.tagext.BodyTag#setBodyContent(javax.servlet.jsp.tagext.BodyContent)
   */
  public void setBodyContent(BodyContent bodyContent)
  {
    if (getTag() instanceof BodyTag) {
      ((BodyTag) getTag()).setBodyContent(bodyContent);
    }
  }
}
